package model.management;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import persistence.PersistenceMode;
import persistence.ProductContainersDAO;
import model.entity.INode;
import model.entity.container.ProductContainer;
import model.entity.container.ProductGroup;
import model.events.EntityEvent;
import model.events.EntityModifiedEvent;
import reports.visitor.IVisitor;
import common.Result;
import common.ValueResult;

/**
 * Track the instance of the list of product group
 * 
 * @author Ian Ryu
 * 
 *         {@invariant productGroups.size() >= 0}
 * 
 */

public class ProductGroupManager extends model.events.EntityEventEmitter implements INode
{

  private static ProductGroupManager INSTANCE = new ProductGroupManager();

  public static ProductGroupManager getInstance()
  {
    return INSTANCE;
  }

  static List<ProductGroup> productGroups = new ArrayList<ProductGroup>();

  private ProductGroupManager()
  {
    _load();
  }

  /**
   * Validates whether the given product group can be added to product group.
   * 
   * @param productgroup
   *          The product group to validate.
   * 
   * @return a result, whose status indicates whether the given product group has valid attributes.
   * 
   *         {@pre productgroup != null}
   * 
   */
  public static Result validateAddProductGroup(ProductGroup productgroup)
  {
    ProductContainer container = productgroup.getContainer();
    if (container == null)
      return new Result(false, "Cannot add Product Group - null container!");

    Iterator<ProductGroup> itr = container.getProductGroups();
    while (itr.hasNext())
    {
      ProductGroup group = itr.next();
      if (group.getName().equals(productgroup.getName()))
        return new Result(false, "Cannot add Product Group - "
            + "one already exists with that name in the given container!");
    }

    return new Result(true);
  }

  /**
   * Validates whether the given product group can be added to this product container. If so, the
   * product group is added to this container.
   * 
   * @param productgroup
   *          The product group to be added to this container.
   * 
   * @return a result, indicating whether this operation was successful or not.
   * 
   *         {@pre productgroup != null} {@pre productGroups.size() = (old)productGroups.size() +
   *         1}
   */
  public static Result addProductGroup(ProductGroup productgroup)
  {
    Result canAdd = validateAddProductGroup(productgroup);
    if (!canAdd.getStatus())
    {
      return canAdd;
    }

    productgroup.getContainer().addProductGroup(productgroup);
    productGroups.add(productgroup);
    save();
    ProductContainersDAO dao = PersistenceMode.getSystemMode().getProductContainersDAO();
    dao.add(productgroup);
    INSTANCE.fireAddedEvent(new EntityEvent(productgroup, EntityEvent.ENTITY_ADDED, null,
        productgroup.getContainer()));

    return new Result(true);
  }

  /**
   * Validates whether the given product group can be removed from product group.
   * 
   * @param productGroup
   *          The product group to validate.
   * 
   * @return a result, whose status indicates whether the given product group can be removed from
   *         this container.
   * 
   *         {@pre productgroup != null} {@post retval != null}
   * 
   */
  public static Result validateRemoveProductGroup(ProductGroup productGroup)
  {
    Result result;
    if (productGroup == null)
    {
      result = new Result(false, "Cannot remove Product Group - null instance");
    }
    else if (!productGroups.contains(productGroup))
    {
      result = new Result(false, "Cannot remove Product Group - it is already removed!");
    }
    else if (productGroup.getItems().hasNext())
    {
      result = new Result(false, "Cannot remove Product Group - it still contains items!");
    }
    else
    {
      result = new Result(true);
    }

    // System.out.println("ProductGroupManager.validateRemoveProductGroup(ProductGroup)"
    // + " is returning:\n\t" + result);

    return result;
  }

  /**
   * Validates whether the given product group can be removed from this product group container. If
   * so, the product group is removed from this container.
   * 
   * @param productgroup
   *          The product group to be removed from this container.
   * 
   * @return a result, indicating whether this operation was successful or not.
   * 
   *         {@pre productgroup != null} {@pre productGroup.size() = (old)productGroups.size() - 1}
   * 
   */
  public static Result removeProductGroup(ProductGroup productgroup)
  {
    Result canRemove = validateRemoveProductGroup(productgroup);
    if (!canRemove.getStatus())
    {
      return canRemove;
    }

    productGroups.remove(productgroup);
    productgroup.getContainer().removeProductGroup(productgroup);
    save();
    EntityEvent event = generateProductGroupRemovedEvent(productgroup);
    INSTANCE.fireRemovedEvent(event);

    return new Result(true);
  }

  private static EntityEvent generateProductGroupRemovedEvent(ProductGroup productgroup)
  {
    return new EntityEvent(productgroup, EntityEvent.ENTITY_REMOVED, productgroup.getContainer(),
        null);
  }

  private static EntityModifiedEvent generateProductGroupModifiedEvent(ProductGroup before,
      ProductGroup after)
  {
    return new EntityModifiedEvent(before, after);
  }

  /**
   * Validates whether the given product group can be updated.
   * 
   * @param productgroup
   *          The product group to validate.
   * 
   * @return a result, whose status indicates whether the given product group can be updated.
   * 
   *         {@pre productgroup != null}
   * 
   */
  public static Result validateUpdateProductGroup(ProductGroup before, ProductGroup after)
  {
    if (before.equals(after))
      return new Result(true, "Pointless update - the before and after units are equal!");
    if (before.getContainer() == null)
      return new Result(false, "Cannot update ProductGroup - before instance does not exist!");
    if (after.getContainer().containsProductGroup(after))
      return new Result(false, "Cannot update ProductGroup - after instance already exists!");
    if (!ProductContainer.isValidName(after.getName()))
      return new Result(false, "Cannot update ProductGroup - after name is not valid!");

    return new Result(true);
  }

  /**
   * Validates whether the given product group can be updated this product container. If so, the
   * product group is updated.
   * 
   * @param productgroup
   *          The product group to be updated this container.
   * 
   * @return a result, indicating whether this operation was successful or not.
   * 
   *         {@pre productgroup != null} {@post productGroups.size == (old)productGroups.size()}
   * 
   */
  public static Result updateProductGroup(ProductGroup before, ProductGroup after)
  {
    Result result = validateUpdateProductGroup(before, after);
    if (result.getStatus() == false)
      return result;
    before.updateTo(after);
    save();
    ProductContainersDAO dao = PersistenceMode.getSystemMode().getProductContainersDAO();
    dao.update(before);
    EntityModifiedEvent event = generateProductGroupModifiedEvent(before, after);
    INSTANCE.fireModifiedEvent(event);

    return new Result(true);
  }

  /**
   * @return An iterator of all <code>Product</code>s in the system.
   */
  public Iterator<ProductGroup> getIterator()
  {
    return Collections.unmodifiableList(productGroups).iterator();
  }

  /**
   * Clears all ProductGroup data.
   */
  public static void clear()
  {
    productGroups.clear();
  }

  public static void save()
  {
    ProductContainersDAO dao = persistence.PersistenceMode.getSystemMode()
        .getProductContainersDAO();
    dao.saveProductGroups(productGroups);
  }

  private void _load()
  {
    ProductContainersDAO dao = persistence.PersistenceMode.getSystemMode()
        .getProductContainersDAO();
    ValueResult<List<ProductGroup>> result = dao.getProductGroups();
    if (!result.getStatus())
    {
      System.err.println("Error loading product groups: " + result.getMessage());
      return;
    }
    productGroups = result.getValue();
  }

  public static void load()
  {
    INSTANCE._load();
  }

  @Override
  public void preOrderAccept(IVisitor visitor)
  {
    for (ProductGroup productgroup : productGroups)
      productgroup.preOrderAccept(visitor);
  }

  @Override
  public void postOrderAccept(IVisitor visitor)
  {
    for (ProductGroup productgroup : productGroups)
      productgroup.preOrderAccept(visitor);
  }
}
